//-------------------------------------------------------------------------------
//	file CAudioCD.cpp
//	brief
//		CAudioCD implementation file
//
//	$Id: CAudioCD.cpp 241 2014-02-09 07:50:17Z ac.verbeck@gmail.com $
//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
//	Copyright and Disclaimer Notice
//
//	Copyright (c) 2013-2014 A.C. Verbeck
//	original author: Michel Helms (MichelHelms@Web.de).
//
//	This file is part of AudioCD: Audio CD function library
//
//	AudioCD is free software: you can redistribute it and/or modify
//	it under the terms of the GNU General Public License as published by
//	the Free Software Foundation, either version 3 of the License, or
//	(at your option) any later version.
//
//	AudioCD is distributed in the hope that it will be useful,
//	but WITHOUT ANY WARRANTY; without even the implied warranty of
//	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//	GNU General Public License for more details.
//
//	You should have received a copy of the GNU General Public License
//	along with AudioCD.  If not, see <http://www.gnu.org/licenses/>.
//
//------------------------------------------------------------------------------
#include <windows.h>
#include <winioctl.h>

#include "cd.h"
#include "CWaveFileHeader.h"
#include "CAudioCD.h"

//------------------------------------------------------------------------------
//	Local function
//------------------------------------------------------------------------------
static ULONG AddressToSectors(UCHAR Addr[4])
{
	ULONG Sectors = (Addr[1] * (CD_BLOCKS_PER_SECOND*60)) + (Addr[2]*CD_BLOCKS_PER_SECOND) + Addr[3];
	return Sectors - 150;
}

//------------------------------------------------------------------------------
// CAudioCD class reads audio-tracks from CD.
// Basic functions only:
// - Constructor / Destructor
// - Open / Close / IsOpened
// - GetTrackCount / GetTrackTime / GetTrackSize
// - ReadTrack / ExtractTrack
// - LockCD / UnLockCD
// - LoadCD / EjectCD / IsCDReady
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
// Constructor / Destructor
//------------------------------------------------------------------------------
CAudioCD::CAudioCD(char Drive)
{
	m_hCD = NULL;
	if (Drive != '\0')
		Open(Drive);
}
CAudioCD::~CAudioCD(void)
{
	Close();
}

//------------------------------------------------------------------------------
// Open / Close / IsOpened
//------------------------------------------------------------------------------
bool CAudioCD::Open(char Drive)
{
	Close();

	// Open drive-handle
	char Fn[8] = { '\\', '\\', '.', '\\', Drive, ':', '\0' };
	m_hCD = CreateFile(Fn, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
	if (m_hCD == INVALID_HANDLE_VALUE) {
		m_hCD = NULL;
		return false;
	}

	// Lock drive
	if (LockCD() == false) {
		UnlockCD();
		CloseHandle(m_hCD);
		m_hCD = NULL;
		return false;
	}

	// Get track table and add it to the internal array
	ULONG BytesRead;
	CDROM_TOC Table;
	bool rVal;

	rVal = DeviceIoControl(m_hCD, IOCTL_CDROM_READ_TOC, NULL, 0, &Table, sizeof(Table), &BytesRead, NULL);
	if (rVal == false) {
		UnlockCD();
		CloseHandle(m_hCD);
		m_hCD = NULL;
		return false;
	}
	for (ULONG i=Table.FirstTrack-1; i<Table.LastTrack; i++) {
		CDTRACK NewTrack;
		NewTrack.Address = AddressToSectors(Table.TrackData[i].Address);
		NewTrack.Length = AddressToSectors(Table.TrackData[i+1].Address) - NewTrack.Address;
		m_aTracks.push_back(NewTrack);
	}

	// Return if track-count > 0
	return (m_aTracks.size() > 0) ? true : false;
}

void CAudioCD::Close(void)
{
	UnlockCD();
	m_aTracks.clear();
	CloseHandle(m_hCD);
	m_hCD = NULL;
}

bool CAudioCD::IsOpened()
{
	return (m_hCD != NULL) ? true : false;
}

//------------------------------------------------------------------------------
// GetTrackCount / GetTrackTime / GetTrackSize
//------------------------------------------------------------------------------
ULONG CAudioCD::GetTrackCount()
{
	return (m_hCD == NULL) ? 0xFFFFFFFF : m_aTracks.size();
}

ULONG CAudioCD::GetTrackTime(ULONG Track)
{
	if (m_hCD == NULL)
		return 0xFFFFFFFF;
	if (Track >= m_aTracks.size())
		return 0xFFFFFFFF;

	CDTRACK& Tr = m_aTracks.at(Track);
	return Tr.Length / CD_BLOCKS_PER_SECOND;
}

ULONG CAudioCD::GetTrackSize(ULONG Track)
{
	if (m_hCD == NULL)
		return 0xFFFFFFFF;
	if (Track >= m_aTracks.size())
		return 0xFFFFFFFF;

	CDTRACK& Tr = m_aTracks.at(Track);
	return Tr.Length * RAW_SECTOR_SIZE;
}

//------------------------------------------------------------------------------
// ReadTrack / ExtractTrack
//------------------------------------------------------------------------------
bool CAudioCD::ReadTrack(ULONG TrackNr, CBuf<char>* pBuf)
{
	ULONG i,Dummy;
	bool io_rVal;


	if (m_hCD == NULL)	return false;
	if (TrackNr >= m_aTracks.size()) return false;

	CDTRACK& Track = m_aTracks.at(TrackNr);
	pBuf->Alloc(Track.Length*RAW_SECTOR_SIZE);

	RAW_READ_INFO Info;
	Info.TrackMode = CDDA;
	Info.SectorCount = SECTORS_AT_READ;

	for (i=0; i<Track.Length/SECTORS_AT_READ; i++) {
		Info.DiskOffset.QuadPart = (Track.Address + i*SECTORS_AT_READ) * CD_SECTOR_SIZE;
		io_rVal = DeviceIoControl(m_hCD, IOCTL_CDROM_RAW_READ, &Info, sizeof(Info), pBuf->Ptr()+i*SECTORS_AT_READ*RAW_SECTOR_SIZE, SECTORS_AT_READ*RAW_SECTOR_SIZE, &Dummy, NULL);
		if (io_rVal == false) {
			pBuf->Free();
			return false;
		}
	}

	Info.SectorCount = Track.Length % SECTORS_AT_READ;
	Info.DiskOffset.QuadPart = (Track.Address + i*SECTORS_AT_READ) * CD_SECTOR_SIZE;
	io_rVal = DeviceIoControl(m_hCD, IOCTL_CDROM_RAW_READ, &Info, sizeof(Info), pBuf->Ptr()+i*SECTORS_AT_READ*RAW_SECTOR_SIZE, Info.SectorCount*RAW_SECTOR_SIZE, &Dummy, NULL);
	if (io_rVal == false) {
		pBuf->Free();
		return false;
	}

	return true;
}

bool CAudioCD::ExtractTrack(ULONG TrackNr, LPCTSTR Path)
{
	ULONG i,Dummy;
	bool io_rVal;

	if (m_hCD == NULL) return false;
	if (TrackNr >= m_aTracks.size()) return false;

	CDTRACK& Track = m_aTracks.at(TrackNr);

	HANDLE hFile = CreateFile(Path, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
	if (hFile == INVALID_HANDLE_VALUE) return false;

	CWaveFileHeader WaveFileHeader(44100, 16, 2, Track.Length*RAW_SECTOR_SIZE);
	WriteFile(hFile, &WaveFileHeader, sizeof(WaveFileHeader), &Dummy, NULL);

	CBuf<char> Buf(SECTORS_AT_READ * RAW_SECTOR_SIZE);

	RAW_READ_INFO Info;
	Info.TrackMode = CDDA;
	Info.SectorCount = SECTORS_AT_READ;
	for (i=0; i<Track.Length/SECTORS_AT_READ; i++) {
		Info.DiskOffset.QuadPart = (Track.Address + i*SECTORS_AT_READ) * CD_SECTOR_SIZE;
		io_rVal = DeviceIoControl(m_hCD, IOCTL_CDROM_RAW_READ, &Info, sizeof(Info), Buf, SECTORS_AT_READ*RAW_SECTOR_SIZE, &Dummy, NULL);
		if (io_rVal == true)
			WriteFile(hFile, Buf, Buf.Size(), &Dummy, NULL);
		else
			return false;
	}

	Info.SectorCount = Track.Length % SECTORS_AT_READ;
	if (Info.SectorCount != 0) {
		Info.DiskOffset.QuadPart = (Track.Address + i*SECTORS_AT_READ) * CD_SECTOR_SIZE;
		io_rVal = DeviceIoControl(m_hCD, IOCTL_CDROM_RAW_READ, &Info, sizeof(Info), Buf, Info.SectorCount*RAW_SECTOR_SIZE, &Dummy, NULL);
		if (io_rVal == true)
			WriteFile(hFile, Buf, Info.SectorCount*RAW_SECTOR_SIZE, &Dummy, NULL);
		else
			return false;
	}

	return CloseHandle(hFile);
}

//------------------------------------------------------------------------------
// LockCD / UnlockCD
//------------------------------------------------------------------------------
bool CAudioCD::LockCD()
{
	if (m_hCD == NULL)
		return false;
	ULONG Dummy;
	PREVENT_MEDIA_REMOVAL pmr = { true };
	return (DeviceIoControl(m_hCD, IOCTL_STORAGE_MEDIA_REMOVAL, &pmr, sizeof(pmr), NULL, 0, &Dummy, NULL) != 0) ? true : false;
}

bool CAudioCD::UnlockCD()
{
	if (m_hCD == NULL)
		return false;
	ULONG Dummy;
	PREVENT_MEDIA_REMOVAL pmr = { false };
	return (DeviceIoControl(m_hCD, IOCTL_STORAGE_MEDIA_REMOVAL, &pmr, sizeof(pmr), NULL, 0, &Dummy, NULL) !=0) ? true : false;
}

//------------------------------------------------------------------------------
// LoadCD / EjectCD / IsCDReady
//------------------------------------------------------------------------------
bool CAudioCD::LoadCD(char drive)
{
	ULONG Dummy = 0;
	char Fn[8] = { '\\', '\\', '.', '\\', drive, ':', '\0' };

	// Open drive-handle
	m_hCD = CreateFile(Fn, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
	if (m_hCD == INVALID_HANDLE_VALUE) {
		m_hCD = NULL;
		return false;
	}
	return (DeviceIoControl(m_hCD, IOCTL_STORAGE_LOAD_MEDIA, NULL, 0, NULL, 0, &Dummy, NULL) != 0) ? true : false;
}

bool CAudioCD::EjectCD()
{
	ULONG Dummy = 0;
	if (m_hCD == NULL) return false;
	return (DeviceIoControl(m_hCD, IOCTL_STORAGE_EJECT_MEDIA, NULL, 0, NULL, 0, &Dummy, NULL) != 0) ? true : false;
}

bool CAudioCD::IsCDReady(char Drive)
{
	ULONG Dummy = 0;
	HANDLE hDrive = NULL;
	char Fn[8] = { '\\', '\\', '.', '\\', Drive, ':', '\0' };

	if (Drive != '\0') {
		if (INVALID_HANDLE_VALUE == (hDrive = CreateFile(Fn, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL)))
			return false;
	} else {
		if (m_hCD == NULL)
			return false;
		hDrive = m_hCD;
	}

	bool Success = DeviceIoControl(hDrive, IOCTL_STORAGE_CHECK_VERIFY2, NULL, 0, NULL, 0, &Dummy, NULL);

	if (m_hCD != hDrive)
		CloseHandle(hDrive);

	return Success;
}

//
//	End: CAudioCD.cpp
//
